#pragma once

#include <linux/types.h>
#include <asm/page.h>
#include <riscv/pgtable-bits.h>

#define PGDIR_SHIFT_L3 30
#define PGDIR_SHIFT_L4 39
#define PGDIR_SHIFT_L5 48
#define PGDIR_SHIFT (PGDIR_SHIFT_L3)

/* Size of region mapped by a page global directory */
#define PGDIR_SIZE (1 << PGDIR_SHIFT)
#define PGDIR_MASK (~(PGDIR_SIZE - 1))

#define PMD_SHIFT 21
/* Size of region mapped by a page middle directory */
#define PMD_SIZE (1 << PMD_SHIFT)
#define PMD_MASK (~(PMD_SIZE - 1))

static inline int pgd_bad(pgd_t pgd)
{
    // TODO
    return 0;
}

static inline pgd_t pfn_pgd(unsigned long pfn, pgprot_t prot)
{
    unsigned long prot_val = pgprot_val(prot);

    return __pgd((pfn << _PAGE_PFN_SHIFT) | prot_val);
}

static inline int pte_none(pte_t pte)
{
    return (pte_val(pte) == 0);
}

#define _PAGE_KERNEL                            \
    (_PAGE_READ | _PAGE_WRITE | _PAGE_PRESENT | \
     _PAGE_ACCESSED | _PAGE_DIRTY | _PAGE_GLOBAL)

#define PAGE_KERNEL_EXEC __pgprot(_PAGE_KERNEL | _PAGE_EXEC)
#define PAGE_TABLE __pgprot(_PAGE_TABLE)

#define PTRS_PER_PGD (PAGE_SIZE / sizeof(pgd_t))
#define PTRS_PER_PMD (PAGE_SIZE / sizeof(pmd_t))
/* Number of entries in the page table */
#define PTRS_PER_PTE (PAGE_SIZE / sizeof(pte_t))

#define pmd_index(addr) (((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))

#define pmd_val(x) ((x).pmd)
#define __pmd(x) ((pmd_t){(x)})

static inline int pmd_none(pmd_t pmd)
{
    return (pmd_val(pmd) == 0);
}

static inline pmd_t pfn_pmd(unsigned long pfn, pgprot_t prot)
{
    unsigned long prot_val = pgprot_val(prot);

    return __pmd((pfn << _PAGE_PFN_SHIFT) | prot_val);
}

static inline int pgd_none(pgd_t pgd)
{
    return 0;
}

// todo
#define VMALLOC_SIZE (0x2000000)
#define VMALLOC_END 0x92000000
#define VMALLOC_START (0x90000000 - VMALLOC_SIZE)

/* p4d is folded into pgd in case of 4-level page table */
#define P4D_SHIFT_L3 30
#define P4D_SHIFT_L4 39
#define P4D_SHIFT_L5 39
#define P4D_SHIFT (P4D_SHIFT_L3)
#define P4D_SIZE (1 << P4D_SHIFT)
#define P4D_MASK (~(P4D_SIZE - 1))

extern pgd_t swapper_pg_dir[];

#define pgprot_nx pgprot_nx
static inline pgprot_t pgprot_nx(pgprot_t _prot)
{
    return 0; // todo
}

/* pud is folded into pgd in case of 3-level page table */
#define PUD_SHIFT 30
#define PUD_SIZE (1 << PUD_SHIFT)
#define PUD_MASK (~(PUD_SIZE - 1))

p4d_t *p4d_offset(pgd_t *pgd, unsigned long address);
#define p4d_offset p4d_offset

/*
 * Certain architectures need to do special things when PTEs within
 * a page table are directly modified.  Thus, the following hook is
 * made available.
 */
static inline void set_pte(pte_t *ptep, pte_t pteval)
{
    *ptep = pteval;
}

#define PFN_PTE_SHIFT _PAGE_PFN_SHIFT

/* Constructs a page table entry */
static inline pte_t pfn_pte(unsigned long pfn, pgprot_t prot)
{
	unsigned long prot_val = pgprot_val(prot);

	return __pte((pfn << _PAGE_PFN_SHIFT) | prot_val);
}

static inline int p4d_none(p4d_t p4d)
{
    return 0;
}

static inline int pfn_valid(unsigned long pfn)
{
    return 1;//todo
}

#define pud_val(x)      ((x).pud)

static inline int pud_none(pud_t pud)
{
	return (pud_val(pud) == 0);
}
